﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using KyFileSyncTransfer.Business;
using System.Diagnostics;
using System.Net.Http;
using System.Collections.Concurrent;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net.Http.Headers;


namespace KyFileSyncTransfer
{
    public partial class FrmMain : Form
    {
        private const string appVersion = "16.1215.1";
        private FormWindowState thisFormWindowState;
        private System.Timers.Timer appTimer = null;

        private static int syncFlag = 0;
        private static object syncObj = new object();
        private static DateTime lastRunTime = DateTime.MinValue;

        private int runInterval = 10;

        private string srcFileApiUrl = null;
        private List<WebApiUrlInfo> destFileApiUrlList = null;

        private const string success = "success";
        private const string failure = "failure";
        private const string RunInterval = "RunInterval";
        private const string SrcFileApiUrl = "SrcFileApiUrl";
        private const string DestFileApiUrls = "DestFileApiUrls";

        public FrmMain()
        {
            InitializeComponent();
        }


        #region 自定义方法区域


        private void ExecuteFileTransfer()
        {
            List<string> srcFileNameList = new List<string>();
            var needSyncTransferFilesDatas = GetNeedSyncTransferFilesDatas(srcFileNameList, destFileApiUrlList.Count).Result;


            WriteLog(string.Format("从源服务器目录Http Url:{0}，获取到{1}个需要同步的文件.", srcFileApiUrl, srcFileNameList.Count));

            if (needSyncTransferFilesDatas == null || srcFileNameList.Count <= 0) return;

            ShowFileInfoLogs("需要同步的文件列表如下：", srcFileNameList);

            var fileTransferResultBag = new ConcurrentBag<Dictionary<string, Dictionary<string, string>>>();

            Parallel.ForEach(destFileApiUrlList, (destFileApiUrl) =>
            {
                MultipartContent needSyncTransferFilesData = null;
                if (needSyncTransferFilesDatas.TryTake(out needSyncTransferFilesData))
                {
                    var savedResult = new Dictionary<string, Dictionary<string, string>>();
                    try
                    {
                        savedResult = SyncTransferFiles(destFileApiUrl.Url, needSyncTransferFilesData).Result;
                    }
                    catch (Exception ex)
                    {
                        while (ex.InnerException != null)
                        {
                            ex = ex.InnerException;
                        }

                        savedResult[success] = new Dictionary<string, string>();
                        savedResult[failure] = new Dictionary<string, string>();
                        savedResult[failure][destFileApiUrl.Url] = string.Format("[{0:yyyy-MM-dd HH:mm:ss}] - X 请求：{1} 响应失败，原因：{3}", DateTime.Now, destFileApiUrl, ex.Message);
                    }
                    fileTransferResultBag.Add(savedResult);
                    ShowSyncTransferFileLogs(savedResult);
                }
            });

            #region 同步循环
            //foreach (var destFileApiUrl in destFileApiUrlList)
            //{
            //    MultipartContent needSyncTransferFilesData = null;
            //    if (needSyncTransferFilesDatas.TryTake(out needSyncTransferFilesData))
            //    {
            //        var savedResult = new Dictionary<string, Dictionary<string, string>>();
            //        try
            //        {
            //            savedResult = SyncTransferFiles(destFileApiUrl.Url, needSyncTransferFilesData).Result;
            //        }
            //        catch (Exception ex)
            //        {
            //            while (ex.InnerException != null)
            //            {
            //                ex = ex.InnerException;
            //            }
            //            savedResult[success] = new Dictionary<string, string>();
            //            savedResult[failure] = new Dictionary<string, string>();
            //            savedResult[failure][destFileApiUrl.Url] = string.Format("[{0:yyyy-MM-dd HH:mm:ss}] - X 请求：{1} 响应失败，原因：{2}", DateTime.Now, destFileApiUrl, ex.Message);
            //        }
            //        fileTransferResultBag.Add(savedResult);
            //        ShowSyncTransferFileLogs(savedResult);
            //    }
            //}
            #endregion

            List<string> needRemoveFileNameList = GetNeedRemoveFileNameList(srcFileNameList, fileTransferResultBag.Select(b => b[success]));

            RemoveSourceFiles(needRemoveFileNameList);

            WriteSyncTransferFileLog(GetSyncTransferFileLogList(fileTransferResultBag));

            ShowFileInfoLogs("以下文件已成功同步保存到预设的所有目的服务器目录Http Url中,且已移除在源服务器目录Http Url中的相同文件：", needRemoveFileNameList);

        }

        private void ShowFileInfoLogs(string logTitle, List<string> fileList)
        {
            WriteLog(logTitle);
            foreach (string file in fileList)
            {
                WriteLog(file);
            }
            WriteLog("-".PadRight(30, '-'));
        }

        private void ShowSyncTransferFileLogs(Dictionary<string, Dictionary<string, string>> savedResult)
        {
            foreach (var kv in savedResult)
            {
                bool isError = (kv.Key == failure);
                foreach (var kv2 in kv.Value)
                {
                    WriteLog(kv2.Value, isError);
                }
            }
        }

        private void ReviseFileNames(List<string> fileNameList)
        {
            var regex = new System.Text.RegularExpressions.Regex("^\"+(?<name>.*)\"+$");
            for (int i = 0; i < fileNameList.Count; i++)
            {
                string fileName = fileNameList[i];
                var matchResult = regex.Match(fileName);
                if (matchResult != null && matchResult.Length > 0)
                {
                    fileNameList[i] = matchResult.Groups["name"].Value;
                }
            }
        }


        private string ReviseFileName(string fileName)
        {
            var regex = new System.Text.RegularExpressions.Regex("^\"+(?<name>.*)\"+$");
            var matchResult = regex.Match(fileName);
            if (matchResult != null && matchResult.Length > 0)
            {
                return matchResult.Groups["name"].Value;
            }
            return fileName;
        }

        private List<string> GetSyncTransferFileLogList(IEnumerable<Dictionary<string, Dictionary<string, string>>> savedResult)
        {
            List<string> syncTransferFileLogList = new List<string>();
            foreach (var dic in savedResult)
            {
                foreach (var kv in dic)
                {
                    syncTransferFileLogList.AddRange(kv.Value.Values);
                }
            }

            return syncTransferFileLogList;
        }

        private List<string> GetNeedRemoveFileNameList(List<string> srcFileNameList, IEnumerable<Dictionary<string, string>> successResult)
        {
            List<string> successFileNameList = new List<string>();

            int needTransferFileCount = destFileApiUrlList.Count;

            foreach (var dic in successResult)
            {
                successFileNameList.AddRange(dic.Keys);
            }

            var successFileNames = successFileNameList.GroupBy(f => f).Where(gp => gp.Count() >= needTransferFileCount).Select(gp => gp.Key);

            return srcFileNameList.Where(f => successFileNames.Contains(f, StringComparer.OrdinalIgnoreCase)).ToList();
        }


        private void WriteSyncTransferFileLog(IEnumerable<string> savedResult)
        {
            try
            {
                string apiUrl = srcFileApiUrl + (srcFileApiUrl.EndsWith("/") ? "" : "/") + "/Log" + (srcFileApiUrl.Contains('?') ? "&rid=" : "?rid=") + Guid.NewGuid().ToString("N");
                HttpClient client = new HttpClient();
                client.DefaultRequestHeaders.Add("token", GetToken());
                var result = client.PostAsJsonAsync(apiUrl, savedResult).Result;
                result.EnsureSuccessStatusCode();
            }
            catch (Exception ex)
            {
                WriteLog("WriteSyncTransferFileLog错误:" + ex.Message, true);
            }
        }

        private void RemoveSourceFiles(IEnumerable<string> needRemoveFileNames)
        {
            try
            {
                string apiUrl = srcFileApiUrl + (srcFileApiUrl.Contains('?') ? "&rid=" : "?rid=") + Guid.NewGuid().ToString("N");
                using (HttpClient client = new HttpClient())
                {
                    client.DefaultRequestHeaders.Add("token", GetToken());
                    var result = client.PutAsJsonAsync(apiUrl, needRemoveFileNames).Result;
                    result.EnsureSuccessStatusCode();
                }
            }
            catch (Exception ex)
            {
                WriteLog("RemoveSourceFiles错误:" + ex.Message, true);
            }
        }

        private async Task<Dictionary<string, Dictionary<string, string>>> SyncTransferFiles(string destFileApiUrl, HttpContent filesData)
        {
            string apiUrl = destFileApiUrl + (destFileApiUrl.Contains('?') ? "&rid=" : "?rid=") + Guid.NewGuid().ToString("N");
            using (HttpClient client = new HttpClient())
            {
                client.DefaultRequestHeaders.Add("token", GetToken());
                var result = await client.PostAsync(apiUrl, filesData);
                result.EnsureSuccessStatusCode();
                var savedResult = await result.Content.ReadAsAsync<Dictionary<string, Dictionary<string, string>>>();
                return savedResult;
            }
        }


        private async Task<MultipartContent> GetNeedSyncTransferFilesData(List<string> srcFileNameList)
        {
            string apiUrl = srcFileApiUrl + (srcFileApiUrl.Contains('?') ? "&rid=" : "?rid=") + Guid.NewGuid().ToString("N");
            var filesCont = new MultipartContent();
            using (HttpClient client = new HttpClient())
            {
                client.DefaultRequestHeaders.Add("token", GetToken());

                var result = await client.GetAsync(apiUrl);
                result.EnsureSuccessStatusCode();
                var provider = await result.Content.ReadAsMultipartAsync();
                foreach (var item in provider.Contents)
                {
                    string fileName = item.Headers.ContentDisposition.FileName;
                    if (!string.IsNullOrEmpty(fileName))
                    {
                        filesCont.Add(item);
                        srcFileNameList.Add(ReviseFileName(fileName));
                    }
                }
            }

            return filesCont;
        }


        private async Task<ConcurrentBag<MultipartContent>> GetNeedSyncTransferFilesDatas(List<string> srcFileNameList, int destFileServerCount)
        {
            var multipartContents = new ConcurrentBag<MultipartContent>();

            string apiUrl = srcFileApiUrl + (srcFileApiUrl.Contains('?') ? "&rid=" : "?rid=") + Guid.NewGuid().ToString("N");
            using (HttpClient client = new HttpClient())
            {
                client.DefaultRequestHeaders.Add("token", GetToken());

                var result = await client.GetAsync(apiUrl);
                result.EnsureSuccessStatusCode();

                for (int i = 0; i < destFileServerCount; i++)
                {
                    multipartContents.Add(new MultipartContent());
                }

                var provider = await result.Content.ReadAsMultipartAsync();
                foreach (var item in provider.Contents)
                {
                    string fileName = item.Headers.ContentDisposition.FileName;
                    if (!string.IsNullOrEmpty(fileName))
                    {
                        var bytes = await item.ReadAsByteArrayAsync();

                        foreach (var cont in multipartContents)
                        {
                            cont.Add(CreateByteArrayContent(bytes, fileName, item.Headers.ContentType.MediaType));
                        }

                        srcFileNameList.Add(ReviseFileName(fileName));
                    }
                }
            }

            return multipartContents;
        }


        private HttpContent CreateByteArrayContent(byte[] fileBytes, string fileName, string mediaTypeValue)
        {
            var fileContent = new ByteArrayContent(fileBytes);
            fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
            {
                Name = "files",
                FileName = fileName
            };
            fileContent.Headers.ContentType = new MediaTypeHeaderValue(mediaTypeValue);
            fileContent.Headers.ContentLength = fileBytes.LongLength;

            return fileContent;
        }

        private string GetToken()
        {
            string timeStamp = GetTimeStamp();
            string key = string.Join(string.Empty, "KyFileSyncTransfer.Api".OrderBy(c => c));
            return EncryptUtility.Encrypt(string.Format("{0}-{1}", key, timeStamp));
        }

        /// <summary>
        /// 获取时间戳
        /// </summary>
        /// <returns></returns>
        private string GetTimeStamp()
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }


        private void WriteLog(string msg, bool isError = false)
        {
            if (this.IsHandleCreated && this.InvokeRequired)
            {
                this.Invoke(new Action<string, bool>(WriteLog), msg, isError);
            }
            else
            {
                if (isError && !msg.Contains("Error:"))
                {
                    msg = "Error:" + msg;
                }

                msg = string.Format("{0:yyyy-MM-dd HH:mm:ss} - {1}", DateTime.Now, msg);

                lstLog.Items.Add(msg);
                lstLog.SelectedIndex = lstLog.Items.Count - 1;
            }
        }

        private void SaveAppSettingsToConfig()
        {
            ConfigUtility.RemoveAppSettings(null);
            Dictionary<string, string> settings = new Dictionary<string, string>();
            settings[RunInterval] = txtInterval.Text.Trim();
            settings[SrcFileApiUrl] = txtSrcHttpUrl.Text.Trim();
            settings[DestFileApiUrls] = string.Join(",", destFileApiUrlList.Select(u => u.ToString()));
            ConfigUtility.SetAppSettingValues(settings);
        }

        private void LoadAppSettingsFromConfig()
        {
            var settings = ConfigUtility.GetAppSettingValues();
            foreach (var kv in settings)
            {
                if (kv.Key.Equals(RunInterval, StringComparison.OrdinalIgnoreCase))
                {
                    txtInterval.Text = settings[RunInterval];
                }
                else if (kv.Key.Equals(SrcFileApiUrl, StringComparison.OrdinalIgnoreCase))
                {
                    txtSrcHttpUrl.Text = settings[SrcFileApiUrl];
                }
                else if (kv.Key.Equals(DestFileApiUrls, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(kv.Value))
                {
                    var destFileApiUrls = settings[DestFileApiUrls].Split(new[] { ",", "" }, StringSplitOptions.RemoveEmptyEntries);
                    destFileApiUrlList = destFileApiUrls.Select(u => new WebApiUrlInfo(u) { }).ToList();
                }
            }
        }

        #endregion

        private void FrmMain_Load(object sender, EventArgs e)
        {
            this.Text = string.Format("{0} V{1}", this.Text, appVersion);
            this.notifyIconApp.Text = this.Text;

            destFileApiUrlList = new List<WebApiUrlInfo>();

            LoadAppSettingsFromConfig();

            dgvDestHttpUrls.AutoGenerateColumns = false;
            dgvDestHttpUrls.DataSource = new BindingList<WebApiUrlInfo>(destFileApiUrlList);

            appTimer = new System.Timers.Timer();
            appTimer.Elapsed += appTimer_Elapsed;

            btnStop.Enabled = false;

        }

        private void appTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (Interlocked.Increment(ref syncFlag) == 1) //若不忙则执行(0+1=1表示不忙)
            {
                Stopwatch watch = new Stopwatch();
                do
                {
                    watch.Restart();
                    try
                    {
                        ExecuteFileTransfer();
                    }
                    catch (Exception ex)
                    {
                        WriteLog("执行文件同步传输时发生错误：" + ex.Message, true);
                    }
                    watch.Stop();

                } while (watch.ElapsedMilliseconds >= runInterval); //如果执行的时间超过同步频率间隔，则直接再执行一次

                Interlocked.Exchange(ref syncFlag, 0); //解除忙
            }
        }

        private void lstLog_DrawItem(object sender, DrawItemEventArgs e)
        {
            if (e.Index < 0) return;
            e.DrawBackground();
            string itemValue = lstLog.Items[e.Index].ToString();
            if (itemValue.Contains("Error:"))//如果Error，则红字显示
            {
                e.Graphics.DrawString(itemValue, e.Font, new SolidBrush(Color.Red), e.Bounds);
            }
            else
            {
                e.Graphics.DrawString(itemValue, e.Font, new SolidBrush(e.ForeColor), e.Bounds);
            }
            e.DrawFocusRectangle();
        }

        private void notifyIconApp_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (WindowState == FormWindowState.Minimized)
            {
                WindowState = thisFormWindowState;
                this.ShowInTaskbar = true;
            }
        }

        private void FrmMain_SizeChanged(object sender, EventArgs e)
        {
            //判断是否选择的是最小化按钮
            if (WindowState == FormWindowState.Minimized)
            {
                //隐藏任务栏区图标
                this.ShowInTaskbar = false;
                //图标显示在托盘区
                this.notifyIconApp.Visible = true;
            }
            else
            {
                thisFormWindowState = WindowState;
            }
        }

        private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (MessageBox.Show("您确定要退出程序吗？", "退出确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
            {
                e.Cancel = true;
                return;
            }

            this.notifyIconApp.Visible = false;
            SaveAppSettingsToConfig();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            try
            {
                srcFileApiUrl = txtSrcHttpUrl.Text.Trim();
                if (string.IsNullOrEmpty(srcFileApiUrl))
                {
                    throw new Exception("源服务器目录Http Url不能为空！");
                }

                if (destFileApiUrlList.Count <= 0)
                {
                    throw new Exception("目的服务器目录Http Url列表不能为空！");
                }

                if (!int.TryParse(txtInterval.Text, out runInterval) || runInterval < 10)
                {
                    throw new Exception("时间间隔不正确，必需是整数且>=10！");
                }

                runInterval = runInterval * 1000;
                appTimer.Interval = runInterval;
                appTimer.Start();

                txtSrcHttpUrl.Enabled = false;
                dgvDestHttpUrls.Enabled = false;
                txtInterval.Enabled = false;
                btnStart.Enabled = false;
                btnStop.Enabled = true;

            }
            catch (Exception ex)
            {
                MessageBox.Show("发生错误：" + ex.Message, "错误提示");
            }
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            appTimer.Stop();
            Interlocked.Exchange(ref syncFlag, 0);

            txtSrcHttpUrl.Enabled = true;
            dgvDestHttpUrls.Enabled = true;
            txtInterval.Enabled = true;

            btnStart.Enabled = true;
            btnStop.Enabled = false;
        }



    }
}
